const utils = require('../utils')
const {
  snake2camelJson,
  camel2snakeJson
} = require('../../shared/index')

export default class ProfitSharing {
  constructor (options = {}) {
    if (!options.appId) throw new Error('appId required')
    if (!options.mchId) throw new Error('mchId required')
    if (!options.key) throw new Error('key required')

    options.signType = options.signType || 'HMAC-SHA256'
    this.options = Object.assign({}, options)

    this.baseUrl = 'https://api.mch.weixin.qq.com'
    this.paths = {
      addReceiver: '/pay/profitsharingaddreceiver',
      removeReceiver: '/pay/profitsharingremovereceiver',
      query: '/pay/profitsharingquery',
      merchantRatioQuery: '/pay/profitsharingmerchantratioquery',
      orderAmountQuery: '/pay/profitsharingorderamountquery',
      share: '/secapi/pay/profitsharing',
      multiShare: '/secapi/pay/multiprofitsharing',
      refund: '/secapi/pay/refund',
      finish: '/secapi/pay/profitsharingfinish',
      return: '/secapi/pay/profitsharingreturn',
      returnQuery: '/pay/profitsharingreturnquery'
    }
  }

  _getSign (params, type) {
    const str = utils.getSignStr(params) + '&key=' + this.options.key
    switch (type) {
      case 'MD5':
        return utils.md5(str).toUpperCase()
      case 'HMAC-SHA256':
        return utils.sha256(str, this.options.key).toUpperCase()
      default:
        throw new Error('signType Error')
    }
  }

  _normalizeResult (obj, apiName) {
    obj.returnMsg = obj.returnMsg || (obj.returnCode === 'SUCCESS' ? 'ok' : 'fail')
    obj.errMsg = `payment.${apiName} ${obj.returnMsg.toLowerCase()}`
    return obj
  }

  _parse (xml, apiName, signType) {
    const json = utils.parseXML(xml)
    if (json.return_code === 'FAIL') throw new Error(`${json.return_msg}`)
    if (apiName !== 'getSandboxKey') {
      // if (json.appid !== this.options.appId) throw new Error('appId不匹配')
      // if (json.mch_id !== this.options.mchId) throw new Error('mchId不匹配')
      if (json.sign !== this._getSign(json, signType)) throw new Error('返回结果签名错误')
      // 特殊处理appId
      json.app_id = json.appid
      delete json.appid
    }

    if (json.result_code === 'FAIL') {
      throw new Error(`${json.err_code} ${json.err_code_des}`)
    }
    return this._normalizeResult(snake2camelJson(json), apiName)
  }

  _parseBill (data, apiName) {
    const result = {}
    if (utils.isXML(data)) {
      const json = utils.parseXML(data)
      if (json.return_code === 'FAIL') throw new Error(`${json.return_msg}`)
      if (json.result_code === 'FAIL') throw new Error(`${json.err_code} ${json.err_code_des}`)
    } else {
      result.returnCode = 'SUCCESS'
      result.content = data
    }
    return this._normalizeResult(result, apiName)
  }

  _getPublicParams (publicParams = {}) {
    const {
      appId,
      mchId,
      subAppId,
      subMchId
    } = this.options

    if (Array.isArray(publicParams)) {
      const newOptions = {}
      if (publicParams.indexOf('appid') > -1) {
        newOptions.appid = appId
      }
      if (publicParams.indexOf('mch_id') > -1) {
        newOptions.mchId = mchId
      }
      if (publicParams.indexOf('sub_mch_id') > -1) {
        newOptions.sub_mch_id = subMchId
      }
      if (publicParams.indexOf('sub_appid') > -1) {
        newOptions.sub_appid = subAppId
      }
      newOptions.nonceStr = utils.getNonceStr()
      return newOptions
    } else {
      if (subAppId) {
        return {
          appid: appId,
          mchId: mchId,
          nonceStr: utils.getNonceStr(),
          subAppid: subAppId,
          subMchId
        }
      } else {
        return {
          appid: appId,
          mchId: mchId,
          nonceStr: utils.getNonceStr()
        }
      }
    }
  }

  async _requestWxpay (params, apiName, needPfx = false) {
    if (needPfx && !this.options.pfx) {
      throw new Error('此接口需要微信支付证书（请传入pfx字段）')
    }
    if (apiName !== 'getSandboxKey') {
      await this._initSandbox()
    }
    const signType = params.signType || this.options.signType
    params = camel2snakeJson(params)
    params.sign = this._getSign(params, signType)
    const requestOptions = {
      method: 'POST',
      dataType: 'text',
      data: utils.buildXML(params),
      timeout: this.options.timeout
    }
    console.log('request options:', requestOptions)
    if (needPfx) {
      requestOptions.pfx = this.options.pfx
      requestOptions.passphrase = this.options.mchId
    }
    const {
      status,
      data
    } = await uniCloud.httpclient.request(
      this.options.sandbox ? `${this.baseUrl}/sandboxnew${this.paths[apiName]}`
        : `${this.baseUrl}${this.paths[apiName]}`,
      requestOptions
    )
    if (status !== 200) throw new Error('request fail')

    if (['downloadBill', 'downloadFundflow'].indexOf(apiName) !== -1) {
      return this._parseBill(data, apiName)
    }
    return this._parse(data, apiName, signType)
  }

  // 暂不公开
  async getSandboxKey () {
    const requestOptions = {
      mchId: this.options.mchId,
      nonceStr: utils.getNonceStr()
    }
    const result = await this._requestWxpay(requestOptions, 'getSandboxKey')
    return result
  }

  async _initSandbox () {
    if (this.options.sandbox && !this.options.sandboxKey) {
      this.options.key = this.options.sandboxKey = await this.getSandboxKey()
        .sandbox_signkey
    }
  }

  async query (params, publicParams) {
    // console.log('..............', params, publicParams);
    const requestOptions = {
      ...this._getPublicParams(publicParams),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'query')
    return result
  }

  async addReceiver (receiver) {
    const requestOptions = {
      ...this._getPublicParams(),
      receiver
    }
    const result = await this._requestWxpay(requestOptions, 'addReceiver')
    return result
  }

  async removeReceiver (receiver) {
    const requestOptions = {
      ...this._getPublicParams(),
      receiver
    }
    const result = await this._requestWxpay(requestOptions, 'removeReceiver')
    return result
  }

  async merchantRatioQuery (params, publicParams) {
    const requestOptions = {
      ...this._getPublicParams(publicParams),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'merchantRatioQuery')
    return result
  }

  async orderAmountQuery (params, publicParams) {
    const requestOptions = {
      ...this._getPublicParams(publicParams),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'orderAmountQuery')
    return result
  }

  async share (params) {
    const requestOptions = {
      ...this._getPublicParams(),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'share', true)
    return result
  }

  async multiShare (params) {
    const requestOptions = {
      ...this._getPublicParams(),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'share', true)
    return result
  }

  async refund (params, publicParams) {
    const requestOptions = {
      ...this._getPublicParams(publicParams),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'refund', true)
    return result
  }

  async finish (params, publicParams) {
    const requestOptions = {
      ...this._getPublicParams(publicParams),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'finish', true)
    return result
  }

  async return (params, publicParams) {
    const requestOptions = {
      ...this._getPublicParams(publicParams),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'return', true)
    return result
  }

  async returnQuery (params, publicParams) {
    // console.log('..............', params, publicParams);
    const requestOptions = {
      ...this._getPublicParams(publicParams),
      ...params
    }
    const result = await this._requestWxpay(requestOptions, 'returnQuery')
    return result
  }

  _getNotifyData (postData) {
    let xmlData = postData.body
    if (postData.isBase64Encoded) {
      xmlData = Buffer.from(xmlData, 'base64').toString('utf-8')
    }
    return utils.parseXML(xmlData)
  }

  _verifyNotify (postData, verifySign) {
    const json = this._getNotifyData(postData)
    if (json.return_code === 'FAIL') throw new Error(`${json.return_code} ${json.return_msg}`)
    if (json.appid !== this.options.appId) throw new Error('appId不匹配')
    if (json.mch_id !== this.options.mchId) throw new Error('mchId不匹配')
    if (verifySign && json.sign !== this._getSign(json, this.options.signType)) throw new Error('通知验签未通过')
    const result = snake2camelJson(json)
    // 特别处理一下appid为appId
    result.appId = result.appid
    delete result.appid
    return result
  }

  verifyPaymentNotify (postData) {
    if (this.checkNotifyType(postData) !== 'payment') {
      return false
    }
    return this._verifyNotify(postData, true)
  }

  checkNotifyType (postData) {
    const json = this._getNotifyData(postData)
    if ('total_fee' in json) {
      return 'payment'
    } else if ('req_info' in json) {
      return 'refund'
    }
    return 'payment'
  }
}
